package scantask.client.domainLayer;

import gov.nasa.worldwind.layers.AirspaceLayer;

import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import scantask.client.connection.ClientConnectionHundler;
import scantask.client.threeDObjects.PolygonePool;
import scantask.client.threeDObjects.exceptions.PolygoneCreationException;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import scantask.server.domainLayer.Exceptions.ControllerException;
import scantask.server.domainLayer.Exceptions.LoginException;

import scantask.containers.Area;
import scantask.containers.Benchmark;
import scantask.containers.Coordinate;
import scantask.containers.InformationObject;
import scantask.containers.User;
import scantask.containers.WeekDate;
import scantask.containers.exceptions.IllegalBenchmarkNameException;
import scantask.containers.exceptions.InvalidCoordinateValueException;
import scantask.containers.exceptions.InvalidWeekNumberException;
import scantask.containers.exceptions.InvalidYearException;
import scantask.containers.exceptions.MissingLayerException;
import scantask.containers.exceptions.UnInitializeObjectException;
import scantask.containers.operationStanzas.AddBenchmarkStanza;
import scantask.containers.operationStanzas.GetBenchmarksStanza;
import scantask.containers.operationStanzas.GetInformativeDataStanza;
import scantask.containers.operationStanzas.GetValidDatesStanza;
import scantask.containers.operationStanzas.LoginStanza;
import scantask.containers.operationStanzas.LogoutStanza;
import scantask.containers.operationStanzas.Stanza;

public class Controller implements ControllerInterface {
	
	private User _user;
	private Vector<Benchmark> _benchmarks;
	private Vector<WeekDate> _validDates;
	private WeekDate _currentInformationDate;
	
	private Vector<InformationObject> _informativeObjects;
	
	private PolygonePool _polygonePool;
	
	/** this is temporary until real server will implement **/ 
	private ClientConnectionHundler _connection;
	
	public Controller() {
		this._user = null;
		this._benchmarks = new Vector<Benchmark>();
		this._validDates = new Vector<WeekDate>();
		this._currentInformationDate = null;
		this._informativeObjects = new Vector<InformationObject>();
		
		this._polygonePool = new PolygonePool();
		
		//Temporary
		this._connection = new ClientConnectionHundler();
	}

	
	@Override
	public void addBenchmark(String locationName, double x, double y) throws InvalidCoordinateValueException, ControllerException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Add benchmark failed the user must be login in order to add benchmark");
			throw new LoginException();
		}
		Coordinate coordinate = new Coordinate(x, y);
		Stanza operationStanza = new AddBenchmarkStanza(this._user.getUserName(), locationName, coordinate);
		String resultXml = this._connection.performOperation(operationStanza);
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("addBenchmark, getting " + resultXml + " from server");
		if (resultXml.compareTo("ACK") == 0) {
			try {
				this._benchmarks.add(new Benchmark(locationName, coordinate));
			} catch (IllegalBenchmarkNameException e1) {
					//Impossible exception - AdirD
			} catch (UnInitializeObjectException e1) {
				//Impossible exception - AdirD
			}
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Add benchmark succeed");
			return;
		}
		ControllerException e = (ControllerException)this.deParse(resultXml);
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Add benchmark failed due to " + e.getErrorDescription());
		throw e;
	}

	@Override
	public Vector<Benchmark> getBenchmarks() throws LoginException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Get benchmarks failed the user must be login in order to get benchmarks");
			throw new LoginException();
		}
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Sending user benchmarks to presentation layer");
		return this._benchmarks;
	}
	
	@SuppressWarnings("unchecked")
	private void getBenchmarks(String userName) throws ControllerException {
		Stanza operationStanza = new GetBenchmarksStanza(this._user.getUserName());
		String resultXml = this._connection.performOperation(operationStanza);
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("getBenchmarks, getting " + resultXml + " from server");
		Object resultObj = this.deParse(resultXml);
		if (resultObj instanceof ControllerException) {
			this._benchmarks = new Vector<Benchmark>();
			ControllerException e = (ControllerException)resultObj;
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("getBenchmarks failed, due to " + e.getErrorDescription());
			throw e;
		}
		else if (resultObj instanceof Vector){
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("getBenchmarks succeed");
			this._benchmarks = (Vector<Benchmark>)resultObj;
		}
	}

	@Override
	public Set<String> getDataTypes() throws LoginException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Get data types failed the user must be login in order to get data types");
			throw new LoginException();
		}
		Set<String> ans = new HashSet<String>();
		for (int i = 0; i < this._informativeObjects.size(); i++) {
			ans.add(this._informativeObjects.get(i).getType());
		}
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Sending user data types to presentation layer");
		return ans;
	}

	@Override
	public Coordinate getDefaultCoordinate() throws LoginException, UnInitializeObjectException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Get default coordinate failed the user must be login in order to get default coordinate types");
			throw new LoginException();
		}
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Sending user default coordinate to presentation layer");
		return this._user.getDefaultCoordinate();
	}

	@Override
	public String getUserFullName() throws LoginException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Get user full name failed the user must be login in order to get user full name types");
			throw new LoginException();
		}
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Sending user full name to presentation layer");
		return this._user.getFullName();
	}

	@Override
	public Vector<WeekDate> getValidDates() throws LoginException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Get user valid dates failed the user must be login in order to get user valid dates types");
			throw new LoginException();
		}
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Sending user valid dates to presentation layer");
		return this._validDates;
	}
	
	@SuppressWarnings("unchecked")
	private void getValidDates(String userName) throws ControllerException {
		Stanza operationStanza = new GetValidDatesStanza(this._user.getUserName());
		String resultXml = this._connection.performOperation(operationStanza);
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("getValidDates, getting " + resultXml + " from server");
		Object resultObj = this.deParse(resultXml);
		if (resultObj instanceof ControllerException) {
			this._validDates = new Vector<WeekDate>();
			ControllerException e = (ControllerException)resultObj;
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("getValidDates failed, due to " + e.getErrorDescription());
			throw e;
		}
		else if (resultObj instanceof Set){
			Set<WeekDate> datesSet = (Set<WeekDate>)resultObj; 
			this._validDates = new Vector<WeekDate>(datesSet);
			Collections.sort(this._validDates);
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("getValidDates succeed");
		}
	}

	@Override
	public void login(String userName, String password) throws ControllerException {
		String encryptedPass = this.encryptPassword(password);
		Stanza operationStanza = new LoginStanza(userName, encryptedPass);
		String resultXml = this._connection.performOperation(operationStanza);
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("login, getting " + resultXml + " from server");
		Object resultObj = this.deParse(resultXml);
		if (resultObj instanceof User) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("first part of login succeed");
			this._user = (User)resultObj;
			this.getBenchmarks(this._user.getUserName());
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("login, getting benchmarks succeed");
			this.getValidDates(this._user.getUserName());
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("login, getting validDates succeed");
			if (this._validDates.size() == 0) {
				this._informativeObjects = new Vector<InformationObject>();
				return;
			}
			WeekDate date = this.getLastDate();
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("login, last date is, week = " + date.getWeek() + " year = " + date.getYear());
			try {
				this.viewHistory(date.getYear(), date.getWeek());
				scantask.client.utilities.logging.SingletonLogger.getLogger().warning("login, getting informative objects succeed");
			} catch (InvalidWeekNumberException e1) {
				// impossible to reach this point
				e1.printStackTrace();
			} catch (InvalidYearException e1) {
				// impossible to reach this point
				e1.printStackTrace();
			}
			return;
		}
		else if (resultObj instanceof ControllerException) {
			ControllerException e = (ControllerException)resultObj;
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("login failed, due to " + e.getErrorDescription());
			throw e;
		}
	}

	@Override
	public void logout() throws LoginException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("logout failed the user must be login in order to logout");
			throw new LoginException();
		}
		Stanza operationStanza = new LogoutStanza(this._user.getUserName());
		String result = this._connection.performOperation(operationStanza);
		if (result.compareTo("ACK") == 0) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("logout succeed, going to reset all fields");
			this.reset();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public Vector<InformationObject> viewHistory(int year, int week) throws InvalidWeekNumberException, InvalidYearException, ControllerException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("viewHistory failed the user must be login in order to viewHistory");
			throw new LoginException();
		}
		WeekDate date = new WeekDate(week, year);
		// if the request date is the current data date shall not ask it from the server
		if (date.equals(this._currentInformationDate)) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("viewHistory, update information exist, return it without request from server");
			return this._informativeObjects;
		}
		Stanza operationStanza = new GetInformativeDataStanza(this._user.getUserName(), date);
		String resultXml = this._connection.performOperation(operationStanza);
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("viewHistory, getting " + resultXml + " from server");
		Object resultObj = this.deParse(resultXml);
		if (resultObj instanceof ControllerException) {
			ControllerException e = (ControllerException)resultObj;
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("ViewHistory failed, due to " + e.getErrorDescription());
			throw e;
		}
		Vector<InformationObject> infoObjects = (Vector<InformationObject>)resultObj;
		this._informativeObjects = new Vector<InformationObject>();
		for (int i = 0; i < infoObjects.size(); i++) {
			try {
				infoObjects.get(i).buildDisplayedObject(this._polygonePool);
				this._informativeObjects.add(infoObjects.get(i));
			} catch (PolygoneCreationException e) {
				scantask.client.utilities.logging.SingletonLogger.getLogger().warning("login, failed build 3d object to object number " + i);
				scantask.client.utilities.logging.SingletonLogger.getLogger().warning(e.toString());
			}
		}
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Setting current date to week = " + date.getWeek() + " year = " + date.getWeek());
		this._currentInformationDate = date;
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Sending user valid dates to presentation layer");
		return this._informativeObjects;
	}
	
	
	@Override
	public Vector<Area> getUserAreas() throws LoginException, UnInitializeObjectException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("getAreas failed the user must be login in order to get his areas");
			throw new LoginException();
		}
		return this._user.getUserAreas();
	}
	
	
	private WeekDate getLastDate() {
		Collections.sort(this._validDates);
		return this._validDates.lastElement();
	}
	
	private void reset() {
		scantask.client.utilities.logging.SingletonLogger.getLogger().warning("Reset all controller fields, probably due to logout operation");
		this._user = null;
		this._benchmarks = new Vector<Benchmark>();
		this._validDates = new Vector<WeekDate>();
		this._currentInformationDate = null;
		this._informativeObjects = new Vector<InformationObject>();
		this._polygonePool = new PolygonePool();
	}
	
//	private String parse(Stanza operation) {
//		XStream xstream = new XStream(new DomDriver());
//		return xstream.toXML(operation);
//	}
	
	private Object deParse(String resultXml) {
		XStream xstream = new XStream(new DomDriver());
		return xstream.fromXML(resultXml);
	}
	
	private String encryptPassword(String password) {
		// encrypting the password
		String storepass = password;
		try
		{
			java.security.Security.addProvider(new sun.security.provider.Sun());
			java.security.MessageDigest lMessageDigest = java.security.MessageDigest.getInstance("SHA", "SUN");
			byte[] result = lMessageDigest.digest(password.getBytes());
			storepass = new sun.misc.BASE64Encoder().encode(result );
		}
		catch(java.security.NoSuchProviderException nspe)
		{
			// ...
		}
		catch(java.security.NoSuchAlgorithmException nsae)
		{
			// ...
		}

		return storepass;

	}

	
	
	
	
	
	
	public static void main(String[] args) {
/*		Controller controller = new Controller();
		try {
			controller.login("adir", "deri");
		} catch (ControllerException e) {
			e.printStackTrace();
		}
		Vector<InformationObject> ans = null;
		try {
			 ans = controller.viewHistory(2000, 20);
		} catch (InvalidWeekNumberException e) {
			e.printStackTrace();
		} catch (InvalidYearException e) {
			e.printStackTrace();
		} catch (LoginException e) {
			e.printStackTrace();
		}*/
	}


	@Override
	public void setPolygonesLayers(Map<String, AirspaceLayer> layers) throws LoginException, MissingLayerException {
		if (this._user == null) {
			scantask.client.utilities.logging.SingletonLogger.getLogger().warning("setPolygonesLayers failed the user must be login in order to setPolygonesLayers");
			throw new LoginException();
		}
		for (int i = 0; i < this._informativeObjects.size(); i++) {
			this._informativeObjects.get(i).init3DObject(layers);
		}
	}


}
